using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Diagnostics;

struct MyTaskMethodBuilder<T>
{
    MyTask<T> t;
    public MyTask<T> Task { get => t; }
    public static MyTaskMethodBuilder<T> Create() => default(MyTaskMethodBuilder<T>);
    public void Start<S>(ref S m) where S : IAsyncStateMachine => t = new MyTask<T>(m);
    public void SetStateMachine(IAsyncStateMachine m) { }
    public void SetException(Exception e) { }
    public void SetResult(T r)
    {
        Console.WriteLine("SetResult: " + r);
        Console.WriteLine("SetResult:0000");
        t.awaiter.r = r;
        Console.WriteLine("SetResult:1111");
        t.awaiter.IsCompleted = true;
        Console.WriteLine("SetResult:2222");
        if(t.awaiter.pm != null)
        {
            Console.WriteLine("pm.MoveNext " + t.awaiter);
            t.awaiter.pm.MoveNext();
        }
        Console.WriteLine("SetResult:----");
    }
    public void AwaitOnCompleted<A, S>(ref A a, ref S m) where A : INotifyCompletion where S : IAsyncStateMachine
    {
        Console.WriteLine("AwaitOnCompleted " + a);

        if (a is MyPause)
            ((MyPause)(object)a).OnPause(m);
        else
            ((MyAwaiter<T>)(object)a).pm = m;
    }
    public void AwaitUnsafeOnCompleted<A, S>(ref A a, ref S sm) where A : ICriticalNotifyCompletion where S : IAsyncStateMachine { }
}

[AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
class MyTask<T>
{
    public readonly MyAwaiter<T> awaiter;
    public MyTask(IAsyncStateMachine m) => awaiter = new MyAwaiter<T>(m);
    public MyAwaiter<T> GetAwaiter() 
    {
        Console.WriteLine("MyTask.GetAwaiter");
        awaiter.m.MoveNext();
        return awaiter;
    }
}

class MyAwaiter<T> : INotifyCompletion
{
    public IAsyncStateMachine m, pm;
    public T r;
    public bool IsCompleted { get; set; }
    public T GetResult() => r;
    public void OnCompleted(Action c) {}
    public MyAwaiter(IAsyncStateMachine m)
    {
        this.m = m;
        r = default(T);
        IsCompleted = false;
    }
}

class MyPause : INotifyCompletion
{
    readonly Action<IAsyncStateMachine> a;
    public MyPause(Action<IAsyncStateMachine> a) => this.a = a;
    public MyPause GetAwaiter()
    {
        Console.WriteLine("MyPause.GetAwaiter");
        return this;
    }
    public bool IsCompleted => false;
    public void GetResult() { }
    public void OnCompleted(Action c) { }
    public void OnPause(IAsyncStateMachine m) => a.Invoke(m);
}

static class Coroutines
{
    static readonly List<IAsyncStateMachine> waitingCor = new(1_000_000);
    public static void Go(MyTask<int> t) => waitingCor.Add(t.awaiter.m);
    public static void Go(IAsyncStateMachine m) => waitingCor.Add(m);
    public static void Loop()
    {
        for (; ; )
        {
            Console.WriteLine("======");
            int i = waitingCor.Count - 1;
            if (i < 0)
                break;
            var m = waitingCor[i];
            waitingCor.RemoveAt(i);
            m.MoveNext();
        }
    }
}

sealed class IntChan
{
    int value;
    bool hasValue;
    readonly List<IAsyncStateMachine> putWaitings = new();
    readonly List<IAsyncStateMachine> getWaitings = new();

    public async MyTask<int> Put(int v)
    {
        while (hasValue)
            await new MyPause(m => putWaitings.Add(m));
        Console.WriteLine("Put: " + v);
        value = v;
        hasValue = true;
        int i = getWaitings.Count - 1;
        if (i >= 0)
        {
            var m = getWaitings[i];
            getWaitings.RemoveAt(i);
            Coroutines.Go(m);
        }
        return 0;
    }

    public async MyTask<int> Get()
    {
        Console.WriteLine("Get-");
        while (!hasValue)
            await new MyPause(m => getWaitings.Add(m));
        Console.WriteLine("Get: " + value);
        hasValue = false;
        int i = putWaitings.Count - 1;
        if (i >= 0)
        {
            var m = putWaitings[i];
            putWaitings.RemoveAt(i);
            Coroutines.Go(m);
        }
        return value;
    }
}

static class Bench2
{
    static async MyTask<int> f(IntChan output, IntChan input)
    {
        Console.WriteLine("---");
        int v = await input.Get();
        Console.WriteLine("---" + v);
        await output.Put(1 + v);
        return 0;
    }

    const int maxCount = 3;

    static async MyTask<int> test()
    {
        Console.WriteLine($"Started, sending {maxCount} messages.");
        Console.WriteLine();
        var sw = Stopwatch.StartNew();
        try
        {
            var finalOutput = new IntChan();
            var right = finalOutput;
            for (int i = 0; i < maxCount; i++)
            {
                Console.WriteLine(i);

                var left = right;
                right = new IntChan();
                Coroutines.Go(f(left, right));
            }
            Console.WriteLine("xxx");

            await right.Put(100);
            Console.WriteLine("yyy");
            Console.WriteLine(await finalOutput.Get());
        }
        finally
        {
            sw.Stop();
            Console.WriteLine($"Sending {maxCount} messages took {sw.Elapsed.TotalMilliseconds} ms");
        }
        return 0;
    }

    static async MyTask<int> test2()
    {
        await test();
        await test();
        return 0;
    }

    static void Main(string[] args)
    {
        Coroutines.Go(test());
        //        Coroutines.Go(test2());
        //       Coroutines.Go(test2());
        Coroutines.Loop();
    }
}
